ETL ํ์ดํ๋ผ์ธ์์ ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ํ๊ตฌํฉ๋๋ค. ์ ์ ํ์ดํ์ ํตํด ๊ฒฌ๊ณ ํ๊ณ ์ ๋ขฐํ ์ ์์ผ๋ฉฐ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ์ํฌํ๋ก๋ฅผ ๊ตฌํํ์ฌ ๋ฐ์ดํฐ ํ์ง์ ํฅ์ํ๊ณ ์ค๋ฅ๋ฅผ ์ค์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค.
ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ: ์ ๋ฐํ ETL ํ์ดํ๋ผ์ธ ๊ตฌํ
๋์์์ด ์งํํ๋ ๋ฐ์ดํฐ ์์ง๋์ด๋ง ํ๊ฒฝ์์ ์ถ์ถ, ๋ณํ, ๋ก๋(ETL) ํ์ดํ๋ผ์ธ์ ๋ถ์ ๋ฐ ์์ฌ ๊ฒฐ์ ์ ์ํ ๋ฐ์ดํฐ๋ฅผ ํตํฉํ๊ณ ์ค๋นํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ๊ธฐ์กด์ ETL ์ ๊ทผ ๋ฐฉ์์ ์ข ์ข ๋ฐ์ดํฐ ํ์ง, ๋ฐํ์ ์ค๋ฅ, ์ ์ง๋ณด์์ฑ๊ณผ ๊ด๋ จ๋ ๋ฌธ์ ๋ก ์ด๋ ค์์ ๊ฒช์ต๋๋ค. ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ ๊ธฐ์ ์ ์์ฉํ๋ฉด ์ด๋ฌํ ๋ฌธ์ ์ ๋ํ ๊ฐ๋ ฅํ ํด๊ฒฐ์ฑ ์ ์ ๊ณตํ์ฌ ๊ฒฌ๊ณ ํ๊ณ ์ ๋ขฐํ ์ ์์ผ๋ฉฐ ํ์ฅ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ด๋?
ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ์ ์ ํ์ดํ์ ํ์ฉํ์ฌ ETL ํ๋ก์ธ์ค ์ ๋ฐ์ ๊ฑธ์ณ ๋ฐ์ดํฐ๊ฐ ์์ ์คํค๋ง ๋ฐ ์ ์ฝ ์กฐ๊ฑด์ ๋ถํฉํ๋๋ก ๋ณด์ฅํฉ๋๋ค. ์ด๋ฌํ ์ฌ์ ์๋ฐฉ์ ์ ๊ทผ ๋ฐฉ์์ ์ปดํ์ผ ์๊ฐ์ด๋ ์คํ ์ด๊ธฐ ๋จ๊ณ์์ ์ ์ฌ์ ์ธ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ์ฌ ํ์ดํ๋ผ์ธ์ ํตํด ์ ํ๋์ด ๋ค์ด์คํธ๋ฆผ ๋ฐ์ดํฐ๋ฅผ ์์์ํค๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ์ฃผ์ ์ด์ :
- ํฅ์๋ ๋ฐ์ดํฐ ํ์ง: ๊ฐ ๋ณํ ๋จ๊ณ์์ ๋ฐ์ดํฐ ์ ํ ๋ฐ ๊ตฌ์กฐ์ ์ ํจ์ฑ์ ๊ฒ์ฌํ์ฌ ๋ฐ์ดํฐ ์ผ๊ด์ฑ๊ณผ ๋ฌด๊ฒฐ์ฑ์ ๊ฐ์ ํฉ๋๋ค.
- ๋ฐํ์ ์ค๋ฅ ๊ฐ์: ์ ํ ๊ด๋ จ ์ค๋ฅ๋ฅผ ์กฐ๊ธฐ์ ํฌ์ฐฉํ์ฌ ํ์ดํ๋ผ์ธ ์คํ ์ค ์๊ธฐ์น ์์ ์คํจ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
- ํฅ์๋ ์ ์ง๋ณด์์ฑ: ์ฝ๋ ๋ช ํ์ฑ๊ณผ ๊ฐ๋ ์ฑ์ ๊ฐ์ ํ์ฌ ETL ํ์ดํ๋ผ์ธ์ ๋ ์ฝ๊ฒ ์ดํดํ๊ณ ๋๋ฒ๊น ํ๋ฉฐ ์์ ํ ์ ์๋๋ก ํฉ๋๋ค.
- ํฅ์๋ ์ ๋ขฐ๋: ๋ณํ๋ ๋ฐ์ดํฐ์ ์ ํ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๋ํ ๋ ํฐ ํ์ ์ ์ ๊ณตํฉ๋๋ค.
- ๋ ๋์ ํ์ : ๋ช ํํ ๋ฐ์ดํฐ ๊ณ์ฝ์ ์ ๊ณตํ์ฌ ๋ฐ์ดํฐ ์์ง๋์ด์ ๋ฐ์ดํฐ ๊ณผํ์ ๊ฐ์ ํ์ ์ ์ด์งํฉ๋๋ค.
ํ์ ์์ ์ฑ ETL ํ์ดํ๋ผ์ธ ๊ตฌํ: ์ฃผ์ ๊ฐ๋
ํ์ ์์ ์ฑ ETL ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๋ ๋ฐ๋ ๋ช ๊ฐ์ง ํต์ฌ ๊ฐ๋ ๊ณผ ๊ธฐ์ ์ด ํฌํจ๋ฉ๋๋ค.
1. ์คํค๋ง ์ ์ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ
ํ์ ์์ ์ฑ ETL์ ๊ธฐ๋ณธ์ ๋ฐ์ดํฐ์ ๋ํ ๋ช ์์ ์คํค๋ง๋ฅผ ์ ์ํ๋ ๋ฐ ์์ต๋๋ค. ์คํค๋ง๋ ์ด ์ด๋ฆ, ๋ฐ์ดํฐ ์ ํ(์: ์ ์, ๋ฌธ์์ด, ๋ ์ง) ๋ฐ ์ ์ฝ ์กฐ๊ฑด(์: null ์์, ๊ณ ์ )์ ํฌํจํ์ฌ ๋ฐ์ดํฐ์ ๊ตฌ์กฐ์ ๋ฐ์ดํฐ ์ ํ์ ์ค๋ช ํฉ๋๋ค. Apache Avro, Protocol Buffers ๋๋ ์ธ์ด๋ณ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์: Scala์ case ํด๋์ค ๋๋ Python์ Pydantic)์ ๊ฐ์ ์คํค๋ง ์ ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ์ ๊ตฌ์กฐ๋ฅผ ๊ณต์์ ์ผ๋ก ์ ์ธํ ์ ์์ต๋๋ค.
์์:
๊ณ ๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํ๋ค๊ณ ๊ฐ์ ํด ๋ด
์๋ค. Customer ๋ฐ์ดํฐ์ ๋ํ ์คํค๋ง๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ ์ ์์ต๋๋ค.
{
"type": "record",
"name": "Customer",
"fields": [
{"name": "customer_id", "type": "int"},
{"name": "first_name", "type": "string"},
{"name": "last_name", "type": "string"},
{"name": "email", "type": "string"},
{"name": "registration_date", "type": "string"} // ISO 8601 ํ์ ๊ฐ์
]
}
๋ณํ ์ ์ ๋ค์ด์ค๋ ๋ฐ์ดํฐ๋ฅผ ์ด ์คํค๋ง์ ๋น๊ตํ์ฌ ์ ํจ์ฑ์ ๊ฒ์ฌํด์ผ ํฉ๋๋ค. ์ด๋ ๋ฐ์ดํฐ๊ฐ ์์ ๊ตฌ์กฐ ๋ฐ ๋ฐ์ดํฐ ์ ํ์ ๋ถํฉํ๋์ง ํ์ธํฉ๋๋ค. ์คํค๋ง๋ฅผ ์๋ฐํ๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ ๊ฑฐ๋ถ๋๊ฑฐ๋ ์ ์ ํ๊ฒ ์ฒ๋ฆฌ๋์ด์ผ ํฉ๋๋ค(์: ์กฐ์ฌ๋ฅผ ์ํด ๋ก๊น ).
2. ์ ์ ํ์ดํ ๋ฐ ๋ฐ์ดํฐ ๊ณ์ฝ
์ค์นผ๋ผ, ์๋ฐ์ ๊ฐ์ ์ธ์ด์์ ์ ๊ณตํ๊ณ MyPy์ ๊ฐ์ ๋๊ตฌ๋ฅผ ํตํด ํ์ด์ฌ์์๋ ์ ์ ๋ ๋ง์ด ์ฑํ๋๊ณ ์๋ ์ ์ ํ์ดํ์ ํ์ ์์ ์ฑ์ ๊ฐ์ ํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ์ ์ ํ์ ์ ์ฌ์ฉํจ์ผ๋ก์จ ๊ฐ ๋ณํ ๋จ๊ณ์ ์์ ์ ๋ ฅ ๋ฐ ์ถ๋ ฅ ํ์ ์ ์ง์ ํ๋ ๋ฐ์ดํฐ ๊ณ์ฝ์ ์ ์ํ ์ ์์ต๋๋ค.
์์ (์ค์นผ๋ผ):
case class Customer(customerId: Int, firstName: String, lastName: String, email: String, registrationDate: String)
def validateEmail(customer: Customer): Option[Customer] = {
if (customer.email.contains("@") && customer.email.contains(".")) {
Some(customer)
} else {
None // ์๋ชป๋ ์ด๋ฉ์ผ
}
}
์ด ์์์์ validateEmail ํจ์๋ ๋ช
์์ ์ผ๋ก Customer ๊ฐ์ฒด๋ฅผ ์
๋ ฅ์ผ๋ก ๋ฐ๊ณ Option[Customer]๋ฅผ ๋ฐํํ๋ฉฐ, ์ด๋ ์ ํจํ ๊ณ ๊ฐ์ด๊ฑฐ๋ ์๋ฌด๊ฒ๋ ์๋์ ๋ํ๋
๋๋ค. ์ด๋ฅผ ํตํด ์ปดํ์ผ๋ฌ๋ ํจ์๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉ๋๊ณ ์ถ๋ ฅ์ด ์ ์ ํ๊ฒ ์ฒ๋ฆฌ๋๋์ง ํ์ธํ ์ ์์ต๋๋ค.
3. ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์์น
๋ถ๋ณ์ฑ, ์์ ํจ์, ๋ถ์์ฉ ๋ฐฉ์ง ๋ฑ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์์น์ ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ํนํ ์ ํฉํฉ๋๋ค. ๋ถ๋ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ ๋ฐ์ดํฐ๊ฐ ์ ์๋ฆฌ์์ ์์ ๋์ง ์๋๋ก ๋ณด์ฅํ์ฌ ์๊ธฐ์น ์์ ๋ถ์์ฉ์ ๋ฐฉ์งํ๊ณ ๋ณํ ํ๋ก์ธ์ค์ ๋ํด ์ถ๋ก ํ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค. ๋์ผํ ์ ๋ ฅ์ ๋ํด ํญ์ ๋์ผํ ์ถ๋ ฅ์ ๋ฐํํ๊ณ ๋ถ์์ฉ์ด ์๋ ์์ ํจ์๋ ์์ธก ๊ฐ๋ฅ์ฑ๊ณผ ํ ์คํธ ๊ฐ๋ฅ์ฑ์ ๋์ฑ ํฅ์์ํต๋๋ค.
์์ (ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฌ์ฉํ ํ์ด์ฌ):
from typing import NamedTuple, Optional
class Customer(NamedTuple):
customer_id: int
first_name: str
last_name: str
email: str
registration_date: str
def validate_email(customer: Customer) -> Optional[Customer]:
if "@" in customer.email and "." in customer.email:
return customer
else:
return None
์ฌ๊ธฐ์ Customer๋ ๋ถ๋ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ํ๋ด๋ ์ด๋ฆ ์๋ ํํ์
๋๋ค. validate_email ํจ์ ๋ํ ์์ ํจ์๋ก, Customer ๊ฐ์ฒด๋ฅผ ๋ฐ์ ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ์ ๋ฐ๋ผ ์ ํ์ Customer ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ฉฐ, ์๋ณธ Customer ๊ฐ์ฒด๋ฅผ ์์ ํ๊ฑฐ๋ ๋ค๋ฅธ ๋ถ์์ฉ์ ์ผ์ผํค์ง ์์ต๋๋ค.
4. ๋ฐ์ดํฐ ๋ณํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ํ๋ ์์ํฌ
์ฌ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ํ๋ ์์ํฌ๊ฐ ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ์ฉ์ดํ๊ฒ ํฉ๋๋ค. ์ด๋ฌํ ๋๊ตฌ๋ ์ข ์ข ์คํค๋ง ์ ์, ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ, ๋ด์ฅ๋ ํ์ ๊ฒ์ฌ ๊ธฐ๋ฅ์ ๊ฐ์ถ ๋ณํ ํจ์์ ๊ฐ์ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
- ์ค์นผ๋ผ๋ฅผ ์ฌ์ฉํ Apache Spark: ์ค์นผ๋ผ์ ๊ฐ๋ ฅํ ํ์ดํ ์์คํ ๊ณผ ๊ฒฐํฉ๋ Spark๋ ํ์ ์์ ์ฑ ETL ํ์ดํ๋ผ์ธ ๊ตฌ์ถ์ ์ํ ๊ฐ๋ ฅํ ํ๋ซํผ์ ์ ๊ณตํฉ๋๋ค. Spark์ Dataset API๋ ๋ฐ์ดํฐ ๋ณํ์ ์ํ ์ปดํ์ผ ์๊ฐ ํ์ ์์ ์ฑ์ ์ ๊ณตํฉ๋๋ค.
- Apache Beam: Beam์ ๋ฐฐ์น ๋ฐ ์คํธ๋ฆฌ๋ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ชจ๋๋ฅผ ์ํ ํตํฉ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์ ์ ๊ณตํ๋ฉฐ, ๋ค์ํ ์คํ ์์ง(Spark, Flink, Google Cloud Dataflow ํฌํจ)์ ์ง์ํฉ๋๋ค. Beam์ ํ์ ์์คํ ์ ๋ค์ํ ์ฒ๋ฆฌ ๋จ๊ณ์์ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ๋ณด์ฅํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
- dbt (Data Build Tool): ํ๋ก๊ทธ๋๋ฐ ์ธ์ด ์์ฒด๋ ์๋์ง๋ง, dbt๋ SQL๊ณผ Jinja๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ค์์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๊ธฐ ์ํ ํ๋ ์์ํฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ ๋ณต์กํ ๋ณํ ๋ฐ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํด ํ์ ์์ ์ฑ ์ธ์ด์ ํตํฉ๋ ์ ์์ต๋๋ค.
- Pydantic ๋ฐ MyPy๋ฅผ ์ฌ์ฉํ ํ์ด์ฌ: Pydantic์ ํ์ด์ฌ ํ์ ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ์ค์ ๊ด๋ฆฌ๋ฅผ ์ ์ํ ์ ์๋๋ก ํฉ๋๋ค. MyPy๋ ํ์ด์ฌ ์ฝ๋์ ๋ํ ์ ์ ํ์ ๊ฒ์ฌ๋ฅผ ์ ๊ณตํ์ฌ ๋ฐํ์ ์ ์ ํ์ ๊ด๋ จ ์ค๋ฅ๋ฅผ ๊ฐ์งํ ์ ์๋๋ก ํฉ๋๋ค.
ํ์ ์์ ์ฑ ETL ๊ตฌํ์ ์ค์ ์์
๋ค์ํ ๊ธฐ์ ๋ก ํ์ ์์ ์ฑ ETL ํ์ดํ๋ผ์ธ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํด ๋ณด๊ฒ ์ต๋๋ค.
์์ 1: Apache Spark ๋ฐ ์ค์นผ๋ผ๋ฅผ ์ฌ์ฉํ ํ์ ์์ ์ฑ ETL
์ด ์์๋ CSV ํ์ผ์์ ๊ณ ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ , ๋ฏธ๋ฆฌ ์ ์๋ ์คํค๋ง์ ๋น๊ตํ์ฌ ๋ฐ์ดํฐ์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๋ฉฐ, ๋ฐ์ดํฐ๋ฅผ Parquet ํ์ผ๋ก ๋ณํํ๋ ๊ฐ๋จํ ETL ํ์ดํ๋ผ์ธ์ ๋ณด์ฌ์ค๋๋ค. ์ด๋ ์ปดํ์ผ ์๊ฐ ํ์ ์์ ์ฑ์ ์ํด Spark์ Dataset API๋ฅผ ํ์ฉํฉ๋๋ค.
import org.apache.spark.sql.{Dataset, SparkSession}
import org.apache.spark.sql.types._
import org.apache.spark.sql.functions._
case class Customer(customerId: Int, firstName: String, lastName: String, email: String, registrationDate: String)
object TypeSafeETL {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("TypeSafeETL").master("local[*]").getOrCreate()
import spark.implicits._
// ์คํค๋ง ์ ์
val schema = StructType(Array(
StructField("customerId", IntegerType, nullable = false),
StructField("firstName", StringType, nullable = false),
StructField("lastName", StringType, nullable = false),
StructField("email", StringType, nullable = false),
StructField("registrationDate", StringType, nullable = false)
))
// CSV ํ์ผ ์ฝ๊ธฐ
val df = spark.read
.option("header", true)
.schema(schema)
.csv("data/customers.csv")
// Dataset[Customer]๋ก ๋ณํ
val customerDS: Dataset[Customer] = df.as[Customer]
// ๋ณํ: ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ
val validCustomers = customerDS.filter(customer => customer.email.contains("@") && customer.email.contains("."))
// ๋ก๋: Parquet์ ์ฐ๊ธฐ
validCustomers.write.parquet("data/valid_customers.parquet")
spark.stop()
}
}
์ค๋ช :
- ์ฝ๋๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ํ๋ด๋
Customer์ผ์ด์ค ํด๋์ค๋ฅผ ์ ์ํฉ๋๋ค. - ๋ฏธ๋ฆฌ ์ ์๋ ์คํค๋ง๋ฅผ ์ฌ์ฉํ์ฌ CSV ํ์ผ์ ์ฝ์ต๋๋ค.
- DataFrame์ ์ปดํ์ผ ์๊ฐ ํ์
์์ ์ฑ์ ์ ๊ณตํ๋
Dataset[Customer]๋ก ๋ณํํฉ๋๋ค. - ์ ํจํ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ๊ฐ์ง ๊ณ ๊ฐ๋ง ํฌํจํ๋๋ก ๋ฐ์ดํฐ๋ฅผ ํํฐ๋งํฉ๋๋ค.
- ๋ณํ๋ ๋ฐ์ดํฐ๋ฅผ Parquet ํ์ผ์ ์๋๋ค.
์์ 2: ํ์ด์ฌ, Pydantic ๋ฐ MyPy๋ฅผ ์ฌ์ฉํ ํ์ ์์ ์ฑ ETL
์ด ์์๋ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ์ Pydantic์, ์ ์ ํ์ ๊ฒ์ฌ์ MyPy๋ฅผ ์ฌ์ฉํ์ฌ ํ์ด์ฌ์์ ํ์ ์์ ์ฑ์ ๋ฌ์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
from typing import List, Optional
from pydantic import BaseModel, validator
class Customer(BaseModel):
customer_id: int
first_name: str
last_name: str
email: str
registration_date: str
@validator("email")
def email_must_contain_at_and_dot(cls, email: str) -> str:
if "@" not in email or "." not in email:
raise ValueError("Invalid email format")
return email
def load_data(file_path: str) -> List[dict]:
# ํ์ผ์์ ๋ฐ์ดํฐ ์ฝ๊ธฐ ์๋ฎฌ๋ ์ด์
(์ค์ ํ์ผ ์ฝ๊ธฐ๋ก ๊ต์ฒด)
return [
{"customer_id": 1, "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "registration_date": "2023-01-01"},
{"customer_id": 2, "first_name": "Jane", "last_name": "Smith", "email": "jane.smith@example.net", "registration_date": "2023-02-15"},
{"customer_id": 3, "first_name": "Peter", "last_name": "Jones", "email": "peter.jonesexample.com", "registration_date": "2023-03-20"},
]
def transform_data(data: List[dict]) -> List[Customer]:
customers: List[Customer] = []
for row in data:
try:
customer = Customer(**row)
customers.append(customer)
except ValueError as e:
print(f"Error validating row: {row} - {e}")
return customers
def save_data(customers: List[Customer], file_path: str) -> None:
# ํ์ผ์ ๋ฐ์ดํฐ ์ ์ฅ ์๋ฎฌ๋ ์ด์
(์ค์ ํ์ผ ์ฐ๊ธฐ๋ก ๊ต์ฒด)
print(f"Saving {len(customers)} valid customers to {file_path}")
for customer in customers:
print(customer.json())
if __name__ == "__main__":
data = load_data("data/customers.json")
valid_customers = transform_data(data)
save_data(valid_customers, "data/valid_customers.json")
์ค๋ช :
- ์ฝ๋๋ Pydantic์
BaseModel์ ์ฌ์ฉํ์ฌCustomer๋ชจ๋ธ์ ์ ์ํฉ๋๋ค. ์ด ๋ชจ๋ธ์ ๋ฐ์ดํฐ์ ๋ํ ํ์ ์ ์ฝ ์กฐ๊ฑด์ ๊ฐ์ ํฉ๋๋ค. - ์ด๋ฉ์ผ ํ๋์ "@"์ "."์ด ๋ชจ๋ ํฌํจ๋๋๋ก ์ ํจ์ฑ ๊ฒ์ฌ ํจ์๊ฐ ์ฌ์ฉ๋ฉ๋๋ค.
transform_dataํจ์๋ ์ ๋ ฅ ๋ฐ์ดํฐ์์Customer๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ค๊ณ ์๋ํฉ๋๋ค. ๋ฐ์ดํฐ๊ฐ ์คํค๋ง๋ฅผ ๋ฐ๋ฅด์ง ์์ผ๋ฉดValueError๊ฐ ๋ฐ์ํฉ๋๋ค.- MyPy๋ ์ฝ๋๋ฅผ ์ ์ ์ผ๋ก ํ์
๊ฒ์ฌํ๊ณ ๋ฐํ์ ์ ์ ์ ์ฌ์ ์ธ ํ์
์ค๋ฅ๋ฅผ ํฌ์ฐฉํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
mypy your_script.py๋ฅผ ์คํํ์ฌ ํ์ผ์ ํ์ธํ์ธ์.
ํ์ ์์ ์ฑ ETL ํ์ดํ๋ผ์ธ์ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ์ด์ ์ ๊ทน๋ํํ๋ ค๋ฉด ๋ค์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ณ ๋ คํ์ญ์์ค.
- ์คํค๋ง๋ฅผ ์กฐ๊ธฐ์ ์ ์: ๋ฐ์ดํฐ ์๋ณธ ๋ฐ ๋์์ ๋ํ ๋ช ํํ๊ณ ํฌ๊ด์ ์ธ ์คํค๋ง๋ฅผ ์ ์ํ๋ ๋ฐ ์๊ฐ์ ํฌ์ํ์ญ์์ค.
- ๋ชจ๋ ๋จ๊ณ์์ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ: ๊ฐ ๋ณํ ๋จ๊ณ์์ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ ํ์ธ์ ๊ตฌํํ์ฌ ์ค๋ฅ๋ฅผ ์กฐ๊ธฐ์ ํฌ์ฐฉํ์ญ์์ค.
- ์ ์ ํ ๋ฐ์ดํฐ ์ ํ ์ฌ์ฉ: ๋ฐ์ดํฐ๋ฅผ ์ ํํ๊ฒ ๋ํ๋ด๊ณ ํ์์ ๋ฐ๋ผ ์ ์ฝ ์กฐ๊ฑด์ ๊ฐ์ ํ๋ ๋ฐ์ดํฐ ์ ํ์ ์ ํํ์ญ์์ค.
- ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์์ฉ: ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์์น์ ํ์ฉํ์ฌ ์์ธก ๊ฐ๋ฅํ๊ณ ํ ์คํธ ๊ฐ๋ฅํ ๋ณํ์ ์์ฑํ์ญ์์ค.
- ํ ์คํธ ์๋ํ: ETL ํ์ดํ๋ผ์ธ์ ์ ํ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ํฌ๊ด์ ์ธ ๋จ์ ๋ฐ ํตํฉ ํ ์คํธ๋ฅผ ๊ตฌํํ์ญ์์ค.
- ๋ฐ์ดํฐ ํ์ง ๋ชจ๋ํฐ๋ง: ๋ฐ์ดํฐ ํ์ง ์งํ๋ฅผ ์ง์์ ์ผ๋ก ๋ชจ๋ํฐ๋งํ์ฌ ๋ฐ์ดํฐ ๋ฌธ์ ๋ฅผ ์ฌ์ ์ ๊ฐ์งํ๊ณ ํด๊ฒฐํ์ญ์์ค.
- ์ฌ๋ฐ๋ฅธ ๋๊ตฌ ์ ํ: ๊ฐ๋ ฅํ ํ์ ์์ ์ฑ ๋ฐ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ฐ์ดํฐ ๋ณํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ํ๋ ์์ํฌ๋ฅผ ์ ํํ์ญ์์ค.
- ํ์ดํ๋ผ์ธ ๋ฌธ์ํ: ์คํค๋ง ์ ์, ๋ณํ ๋ก์ง ๋ฐ ๋ฐ์ดํฐ ํ์ง ๊ฒ์ฌ๋ฅผ ํฌํจํ์ฌ ETL ํ์ดํ๋ผ์ธ์ ์ฒ ์ ํ ๋ฌธ์ํํ์ญ์์ค. ๋ช ํํ ๋ฌธ์๋ ์ ์ง๋ณด์์ฑ ๋ฐ ํ์ ์ ์ค์ํฉ๋๋ค.
๊ณผ์ ๋ฐ ๊ณ ๋ ค ์ฌํญ
ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ์๋ง์ ์ด์ ์ ์ ๊ณตํ์ง๋ง, ํน์ ๊ณผ์ ๋ฐ ๊ณ ๋ ค ์ฌํญ๋ ์์ต๋๋ค.
- ํ์ต ๊ณก์ : ํ์ ์์ ์ฑ ์ธ์ด ๋ฐ ํ๋ ์์ํฌ๋ฅผ ์ฑํํ๋ ค๋ฉด ๋ฐ์ดํฐ ์์ง๋์ด์๊ฒ ํ์ต ๊ณก์ ์ด ํ์ํ ์ ์์ต๋๋ค.
- ๊ฐ๋ฐ ๋ ธ๋ ฅ ์ฆ๊ฐ: ํ์ ์์ ์ฑ ETL ํ์ดํ๋ผ์ธ์ ๊ตฌํํ๋ ค๋ฉด ๊ธฐ์กด ์ ๊ทผ ๋ฐฉ์์ ๋นํด ์ด๊ธฐ ๊ฐ๋ฐ ๋ ธ๋ ฅ์ด ๋ ๋ง์ด ํ์ํ ์ ์์ต๋๋ค.
- ์ฑ๋ฅ ์ค๋ฒํค๋: ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ํ์ ๊ฒ์ฌ๋ ์ผ๋ถ ์ฑ๋ฅ ์ค๋ฒํค๋๋ฅผ ์ ๋ฐํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ํฅ์๋ ๋ฐ์ดํฐ ํ์ง ๋ฐ ๋ฐํ์ ์ค๋ฅ ๊ฐ์๋ผ๋ ์ด์ ์ด ์ข ์ข ์ด ๋น์ฉ๋ณด๋ค ํฝ๋๋ค.
- ๋ ๊ฑฐ์ ์์คํ ๊ณผ์ ํตํฉ: ๊ฐ๋ ฅํ ํ์ดํ์ ์ง์ํ์ง ์๋ ๋ ๊ฑฐ์ ์์คํ ๊ณผ ํ์ ์์ ์ฑ ETL ํ์ดํ๋ผ์ธ์ ํตํฉํ๋ ๊ฒ์ ์ด๋ ค์ธ ์ ์์ต๋๋ค.
- ์คํค๋ง ์งํ: ์คํค๋ง ์งํ(์ฆ, ์๊ฐ ๊ฒฝ๊ณผ์ ๋ฐ๋ฅธ ๋ฐ์ดํฐ ์คํค๋ง ๋ณ๊ฒฝ)๋ฅผ ์ฒ๋ฆฌํ๋ ค๋ฉด ์ ์คํ ๊ณํ ๋ฐ ๊ตฌํ์ด ํ์ํฉ๋๋ค.
๊ฒฐ๋ก
ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ๊ฒฌ๊ณ ํ๊ณ ์ ๋ขฐํ ์ ์์ผ๋ฉฐ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ETL ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๊ธฐ ์ํ ๊ฐ๋ ฅํ ์ ๊ทผ ๋ฐฉ์์ ๋๋ค. ์ ์ ํ์ดํ, ์คํค๋ง ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์์น์ ํ์ฉํ์ฌ ๋ฐ์ดํฐ ํ์ง์ ํฌ๊ฒ ํฅ์์ํค๊ณ ๋ฐํ์ ์ค๋ฅ๋ฅผ ์ค์ด๋ฉฐ ๋ฐ์ดํฐ ์์ง๋์ด๋ง ์ํฌํ๋ก์ ์ ๋ฐ์ ์ธ ํจ์จ์ฑ์ ๋์ผ ์ ์์ต๋๋ค. ๋ฐ์ดํฐ ๋ณผ๋ฅจ๊ณผ ๋ณต์ก์ฑ์ด ๊ณ์ ์ฆ๊ฐํจ์ ๋ฐ๋ผ ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ์์ฉํ๋ ๊ฒ์ ๋ฐ์ดํฐ ๊ธฐ๋ฐ ํต์ฐฐ๋ ฅ์ ์ ํ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํ๋ ๋ฐ ์ ์ ๋ ์ค์ํด์ง ๊ฒ์ ๋๋ค.
Apache Spark, Apache Beam, Pydantic์ ์ฌ์ฉํ๋ ํ์ด์ฌ ๋๋ ๊ธฐํ ๋ฐ์ดํฐ ๋ณํ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ ๊ด๊ณ์์ด, ํ์ ์์ ์ฑ ๊ดํ์ ETL ํ์ดํ๋ผ์ธ์ ํตํฉํ๋ฉด ๋ณด๋ค ํ๋ ฅ์ ์ด๊ณ ๊ฐ์น ์๋ ๋ฐ์ดํฐ ์ธํ๋ผ๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์ ์ ์๋ ์์์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ณ ๋ คํ์ฌ ํ์ ์์ ์ฑ ๋ฐ์ดํฐ ๋ณํ์ ํฅํ ์ฌ์ ์ ์์ํ๊ณ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ํ์ง์ ๋์ด์ญ์์ค.